package com.xh3140.xwidget.sliding;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Build;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

@SuppressWarnings("unused")
public class SlidingDrawer extends ViewGroup {
  public static final int ORIENTATION_HORIZONTAL = 0;
  public static final int ORIENTATION_VERTICAL = 1;

  private static final int TAP_THRESHOLD = 6;
  private static final float MAXIMUM_TAP_VELOCITY = 100.0f;
  private static final float MAXIMUM_MINOR_VELOCITY = 150.0f;
  private static final float MAXIMUM_MAJOR_VELOCITY = 200.0f;
  private static final float MAXIMUM_ACCELERATION = 2000.0f;
  private static final int VELOCITY_UNITS = 1000;
  private static final int ANIMATION_FRAME_DURATION = 1000 / 60;

  private static final int EXPANDED_FULL_OPEN = -10001;
  private static final int COLLAPSED_FULL_CLOSED = -10002;

  private final boolean mVertical;
  private final boolean mAllowSingleTap;
  private final boolean mAnimateOnClick;
  private final int mBottomOffset;
  private final int mTopOffset;
  private final int mHandleId;
  private final int mContentId;
  private final int mTapThreshold;
  private final int mMaximumTapVelocity;
  private final int mMaximumMinorVelocity;
  private final int mMaximumMajorVelocity;
  private final int mMaximumAcceleration;
  private final int mVelocityUnits;
  private final Rect mRect = new Rect();
  private View mHandle;
  private View mContent;
  private Bitmap mBitmap;
  private Canvas mCanvas;
  private int mHandleHeight;
  private int mHandleWidth;
  private int mTouchDelta;
  private boolean mTracking;
  private boolean mLocked;
  private boolean mExpanded;
  private boolean mAnimating;
  private VelocityTracker mVelocityTracker;
  private OnDrawerOpenListener mOnDrawerOpenListener;
  private OnDrawerCloseListener mOnDrawerCloseListener;
  private OnDrawerScrollListener mOnDrawerScrollListener;
  private float mAnimatedAcceleration;
  private float mAnimatedVelocity;
  private float mAnimationPosition;
  private long mAnimationLastTime;
  private long mCurrentAnimationTime;
  private final Runnable mSlidingRunnable = new Runnable() {
    @Override
    public void run() {
      if (mAnimating) {
        incrementAnimation();
        if (mAnimationPosition >= mBottomOffset + (mVertical ? getHeight() : getWidth()) - 1) {
          mAnimating = false;
          closeDrawer();
        } else if (mAnimationPosition < mTopOffset) {
          mAnimating = false;
          openDrawer();
        } else {
          moveHandle((int) mAnimationPosition);
          mCurrentAnimationTime += ANIMATION_FRAME_DURATION;
          postDelayed(mSlidingRunnable, ANIMATION_FRAME_DURATION);
        }
      }
    }
  };

  public SlidingDrawer(@NonNull Context context, @Nullable AttributeSet attrs) {
    this(context, attrs, 0);
  }

  public SlidingDrawer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    this(context, attrs, defStyleAttr, 0);
  }

  public SlidingDrawer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlidingDrawer, defStyleAttr, defStyleRes);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
      saveAttributeDataForStyleable(context, R.styleable.SlidingDrawer, attrs, a, defStyleAttr, 0);
    }
    try {
      mVertical = a.getInt(R.styleable.SlidingDrawer_orientation, ORIENTATION_VERTICAL) == ORIENTATION_VERTICAL;
      mAnimateOnClick = a.getBoolean(R.styleable.SlidingDrawer_animateOnClick, true);
      mAllowSingleTap = a.getBoolean(R.styleable.SlidingDrawer_allowSingleTap, true);
      mTopOffset = a.getDimensionPixelSize(R.styleable.SlidingDrawer_topOffset, 0);
      mBottomOffset = a.getDimensionPixelSize(R.styleable.SlidingDrawer_bottomOffset, 0);
      final int handleId = a.getResourceId(R.styleable.SlidingDrawer_handle, 0);
      if (handleId == 0) {
        throw new IllegalArgumentException("The handle attribute is required and must refer to a valid child.");
      }
      final int contentId = a.getResourceId(R.styleable.SlidingDrawer_content, 0);
      if (contentId == 0) {
        throw new IllegalArgumentException("The content attribute is required and must refer to a valid child.");
      }
      if (handleId == contentId) {
        throw new IllegalArgumentException("The content and handle attributes must refer to different children.");
      }
      mHandleId = handleId;
      mContentId = contentId;
      final float density = getResources().getDisplayMetrics().density;
      mTapThreshold = (int) (TAP_THRESHOLD * density + 0.5f);
      mMaximumTapVelocity = (int) (MAXIMUM_TAP_VELOCITY * density + 0.5f);
      mMaximumMinorVelocity = (int) (MAXIMUM_MINOR_VELOCITY * density + 0.5f);
      mMaximumMajorVelocity = (int) (MAXIMUM_MAJOR_VELOCITY * density + 0.5f);
      mMaximumAcceleration = (int) (MAXIMUM_ACCELERATION * density + 0.5f);
      mVelocityUnits = (int) (VELOCITY_UNITS * density + 0.5f);
    } finally {
      a.recycle();
    }
  }

  @Override
  protected void onFinishInflate() {
    super.onFinishInflate();
    mHandle = findViewById(mHandleId);
    if (mHandle == null) {
      throw new IllegalArgumentException("The handle attribute is must refer to an existing child.");
    }
    mHandle.setFocusable(true);
    mHandle.setClickable(true);
    mHandle.setOnClickListener(new DrawerToggler());
    mContent = findViewById(mContentId);
    if (mContent == null) {
      throw new IllegalArgumentException("The content attribute is must refer to an existing child.");
    }
    mContent.getViewTreeObserver().addOnGlobalLayoutListener(new ContentViewBitmapCache());
    mContent.setVisibility(View.GONE);
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    final int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    final int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
    if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
      throw new RuntimeException("SlidingDrawer cannot have UNSPECIFIED dimensions");
    }
    measureChild(mHandle, widthMeasureSpec, heightMeasureSpec);
    final int widthMeasureSpecContent;
    final int heightMeasureSpecContent;
    if (mVertical) {
      final int height = heightSpecSize - mHandle.getMeasuredHeight() - mTopOffset;
      widthMeasureSpecContent = MeasureSpec.makeMeasureSpec(widthSpecSize, MeasureSpec.EXACTLY);
      heightMeasureSpecContent = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
      mContent.measure(widthMeasureSpecContent, heightMeasureSpecContent);
    } else {
      final int width = widthSpecSize - mHandle.getMeasuredWidth() - mTopOffset;
      widthMeasureSpecContent = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
      heightMeasureSpecContent = MeasureSpec.makeMeasureSpec(heightSpecSize, MeasureSpec.EXACTLY);
    }
    mContent.measure(widthMeasureSpecContent, heightMeasureSpecContent);
    setMeasuredDimension(widthSpecSize, heightSpecSize);
  }

  @Override
  protected void dispatchDraw(Canvas canvas) {
    final long drawingTime = getDrawingTime();
    drawChild(canvas, mHandle, drawingTime);
    if (mTracking || mAnimating) {
      mCanvas.translate(-mContent.getScrollX(), -mContent.getScrollY());
      mContent.draw(mCanvas);
      if (mVertical) {
        canvas.drawBitmap(mBitmap, 0f, mHandle.getBottom(), null);
      } else {
        canvas.drawBitmap(mBitmap, mHandle.getRight(), 0f, null);
      }
    } else if (mExpanded) {
      drawChild(canvas, mContent, drawingTime);
    }
  }

  @Override
  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    if (mTracking) {
      return;
    }
    final int width = right - left;
    final int height = bottom - top;
    final int childLeft, childTop;
    int childWidth = mHandle.getMeasuredWidth();
    int childHeight = mHandle.getMeasuredHeight();
    if (mVertical) {
      childLeft = (width - childWidth) / 2;
      childTop = mExpanded ? mTopOffset : height - childHeight + mBottomOffset;
      mContent.layout(0, mTopOffset + childHeight, mContent.getMeasuredWidth(), mTopOffset + childHeight + mContent.getMeasuredHeight());
    } else {
      childLeft = mExpanded ? mTopOffset : width - childWidth + mBottomOffset;
      childTop = (height - childHeight) / 2;
      mContent.layout(mTopOffset + childWidth, 0, mTopOffset + childWidth + mContent.getMeasuredWidth(), mContent.getMeasuredHeight());
    }
    mHandle.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
    mHandleHeight = mHandle.getHeight();
    mHandleWidth = mHandle.getWidth();
  }

  @Override
  public boolean onInterceptTouchEvent(MotionEvent event) {
    if (mLocked) {
      return false;
    }
    final int action = event.getAction();
    final int x = (int) event.getX();
    final int y = (int) event.getY();
    mHandle.getHitRect(mRect);
    if (!mTracking && !mRect.contains(x, y)) {
      return false;
    }
    if (action == MotionEvent.ACTION_DOWN) {
      mTracking = true;
      mHandle.setPressed(true);
      prepareContent();
      if (mOnDrawerScrollListener != null) {
        mOnDrawerScrollListener.onScrollStarted();
      }
      if (mVertical) {
        final int top = mHandle.getTop();
        mTouchDelta = y - top;
        prepareTracking(top);
      } else {
        final int left = mHandle.getLeft();
        mTouchDelta = x - left;
        prepareTracking(left);
      }
      if (mVelocityTracker != null) {
        mVelocityTracker.addMovement(event);
      }
    }
    return true;
  }

  @Override
  public boolean performClick() {
    return super.performClick();
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    if (mLocked) {
      return true;
    }
    if (mTracking) {
      if (mVelocityTracker == null) {
        return false;
      }
      mVelocityTracker.addMovement(event);
      final int action = event.getAction();
      switch (action) {
        case MotionEvent.ACTION_MOVE:
          moveHandle((int) (mVertical ? event.getY() : event.getX()) - mTouchDelta);
          break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL: {
          performClick();
          mVelocityTracker.computeCurrentVelocity(mVelocityUnits);
          float yVelocity = mVelocityTracker.getYVelocity();
          float xVelocity = mVelocityTracker.getXVelocity();
          final boolean negative;
          if (mVertical) {
            negative = yVelocity < 0;
            if (xVelocity < 0) {
              xVelocity = -xVelocity;
            }
            if (xVelocity > mMaximumMinorVelocity) {
              xVelocity = mMaximumMinorVelocity;
            }
          } else {
            negative = xVelocity < 0;
            if (yVelocity < 0) {
              yVelocity = -yVelocity;
            }
            if (yVelocity > mMaximumMinorVelocity) {
              yVelocity = mMaximumMinorVelocity;
            }
          }
          float velocity = (float) Math.hypot(xVelocity, yVelocity);
          if (negative) {
            velocity = -velocity;
          }
          final int top = mHandle.getTop();
          final int left = mHandle.getLeft();
          if (Math.abs(velocity) < mMaximumTapVelocity) {
            if (mVertical ? (mExpanded && top < mTapThreshold + mTopOffset)
                    || (!mExpanded && top > mBottomOffset + getBottom() - getTop() - mHandleHeight - mTapThreshold)
                    : (mExpanded && left < mTapThreshold + mTopOffset)
                    || (!mExpanded && left > mBottomOffset + getRight() - getLeft() - mHandleWidth - mTapThreshold)) {
              if (mAllowSingleTap) {
                playSoundEffect(SoundEffectConstants.CLICK);
                if (mExpanded) {
                  animateClose(mVertical ? top : left, true);
                } else {
                  animateOpen(mVertical ? top : left, true);
                }
              } else {
                performFling(mVertical ? top : left, velocity, false, true);
              }
            } else {
              performFling(mVertical ? top : left, velocity, false, true);
            }
          } else {
            performFling(mVertical ? top : left, velocity, false, true);
          }
        }
        break;
      }
    }
    return mTracking || mAnimating || super.onTouchEvent(event);
  }

  private void animateClose(int position, boolean notifyScrollListener) {
    prepareTracking(position);
    performFling(position, mMaximumAcceleration, true, notifyScrollListener);
  }

  private void animateOpen(int position, boolean notifyScrollListener) {
    prepareTracking(position);
    performFling(position, -mMaximumAcceleration, true, notifyScrollListener);
  }

  private void performFling(int position, float velocity, boolean always, boolean notifyScrollListener) {
    mAnimationPosition = position;
    mAnimatedVelocity = velocity;

    if (mExpanded) {
      if (always || (velocity > mMaximumMajorVelocity
              || (position > mTopOffset + (mVertical ? mHandleHeight : mHandleWidth)
              && velocity > -mMaximumMajorVelocity))) {
        // We are expanded, but they didn't move sufficiently to cause
        // us to retract.  Animate back to the expanded position.
        mAnimatedAcceleration = mMaximumAcceleration;
        if (velocity < 0) {
          mAnimatedVelocity = 0;
        }
      } else {
        // We are expanded and are now going to animate away.
        mAnimatedAcceleration = -mMaximumAcceleration;
        if (velocity > 0) {
          mAnimatedVelocity = 0;
        }
      }
    } else {
      if (!always && (velocity > mMaximumMajorVelocity
              || (position > (mVertical ? getHeight() : getWidth()) / 2
              && velocity > -mMaximumMajorVelocity))) {
        // We are collapsed, and they moved enough to allow us to expand.
        mAnimatedAcceleration = mMaximumAcceleration;
        if (velocity < 0) {
          mAnimatedVelocity = 0;
        }
      } else {
        // We are collapsed, but they didn't move sufficiently to cause
        // us to retract.  Animate back to the collapsed position.
        mAnimatedAcceleration = -mMaximumAcceleration;
        if (velocity > 0) {
          mAnimatedVelocity = 0;
        }
      }
    }

    long now = SystemClock.uptimeMillis();
    mAnimationLastTime = now;
    mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
    mAnimating = true;
    removeCallbacks(mSlidingRunnable);
    postDelayed(mSlidingRunnable, ANIMATION_FRAME_DURATION);
    stopTracking(notifyScrollListener);
  }

  private void prepareTracking(int position) {
    mTracking = true;
    mVelocityTracker = VelocityTracker.obtain();
    boolean opening = !mExpanded;
    if (opening) {
      mAnimatedAcceleration = mMaximumAcceleration;
      mAnimatedVelocity = mMaximumMajorVelocity;
      mAnimationPosition = mBottomOffset + (mVertical ? getHeight() - mHandleHeight : getWidth() - mHandleWidth);
      moveHandle((int) mAnimationPosition);
      mAnimating = true;
      removeCallbacks(mSlidingRunnable);
      long now = SystemClock.uptimeMillis();
      mAnimationLastTime = now;
      mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
      mAnimating = true;
    } else {
      if (mAnimating) {
        mAnimating = false;
        removeCallbacks(mSlidingRunnable);
      }
      moveHandle(position);
    }
  }

  private void moveHandle(int position) {
    if (mVertical) {
      if (position == EXPANDED_FULL_OPEN) {
        mHandle.offsetTopAndBottom(mTopOffset - mHandle.getTop());
        invalidate();
      } else if (position == COLLAPSED_FULL_CLOSED) {
        mHandle.offsetTopAndBottom(mBottomOffset + getBottom() - getTop() - mHandleHeight - mHandle.getTop());
        invalidate();
      } else {
        final int top = mHandle.getTop();
        int deltaY = position - top;
        if (position < mTopOffset) {
          deltaY = mTopOffset - top;
        } else if (deltaY > mBottomOffset + getBottom() - getTop() - mHandleHeight - top) {
          deltaY = mBottomOffset + getBottom() - getTop() - mHandleHeight - top;
        }
        mHandle.offsetTopAndBottom(deltaY);
        mHandle.getHitRect(mRect);
        invalidate();
      }
    } else {
      if (position == EXPANDED_FULL_OPEN) {
        mHandle.offsetLeftAndRight(mTopOffset - mHandle.getLeft());
        invalidate();
      } else if (position == COLLAPSED_FULL_CLOSED) {
        mHandle.offsetLeftAndRight(mBottomOffset + getRight() - getLeft() - mHandleWidth - mHandle.getLeft());
        invalidate();
      } else {
        final int left = mHandle.getLeft();
        int deltaX = position - left;
        if (position < mTopOffset) {
          deltaX = mTopOffset - left;
        } else if (deltaX > mBottomOffset + getRight() - getLeft() - mHandleWidth - left) {
          deltaX = mBottomOffset + getRight() - getLeft() - mHandleWidth - left;
        }
        mHandle.offsetLeftAndRight(deltaX);
        mHandle.getHitRect(mRect);
        invalidate();
      }
    }
  }

  private void prepareContent() {
    if (mAnimating) {
      return;
    }
    // Something changed in the content, we need to honor the layout request
    // before creating the cached bitmap
    if (mContent.isLayoutRequested()) {
      if (mVertical) {
        final int childHeight = mHandleHeight;
        final int height = getBottom() - getTop() - childHeight - mTopOffset;
        final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(getRight() - getLeft(), MeasureSpec.EXACTLY);
        final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
        mContent.measure(widthMeasureSpec, heightMeasureSpec);
        mContent.layout(0, mTopOffset + childHeight, mContent.getMeasuredWidth(), mTopOffset + childHeight + mContent.getMeasuredHeight());
      } else {
        final int childWidth = mHandle.getWidth();
        final int width = getRight() - getLeft() - childWidth - mTopOffset;
        final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
        final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(getBottom() - getTop(), MeasureSpec.EXACTLY);
        mContent.measure(widthMeasureSpec, heightMeasureSpec);
        mContent.layout(childWidth + mTopOffset, 0, mTopOffset + childWidth + mContent.getMeasuredWidth(), mContent.getMeasuredHeight());
      }
    }
    // Try only once... we should really loop but it's not a big deal
    // if the draw was cancelled, it will only be temporary anyway
    mContent.getViewTreeObserver().dispatchOnPreDraw();
    mContent.setVisibility(View.GONE);
  }

  private void stopTracking(boolean notifyScrollListener) {
    mHandle.setPressed(false);
    mTracking = false;

    if (notifyScrollListener && mOnDrawerScrollListener != null) {
      mOnDrawerScrollListener.onScrollEnded();
    }

    if (mVelocityTracker != null) {
      mVelocityTracker.recycle();
      mVelocityTracker = null;
    }
  }

  private void incrementAnimation() {
    long now = SystemClock.uptimeMillis();
    float t = (now - mAnimationLastTime) / 1000.0f;                   // ms -> s
    final float v = mAnimatedVelocity;                                // px/s
    final float a = mAnimatedAcceleration;                            // px/s/s
    mAnimationPosition += (v * t) + (0.5f * a * t * t);               // px
    mAnimatedVelocity = v + (a * t);                                  // px/s
    mAnimationLastTime = now;                                         // ms
  }

  public void toggle() {
    if (!mExpanded) {
      openDrawer();
    } else {
      closeDrawer();
    }
    invalidate();
    requestLayout();
  }

  public void animateToggle() {
    if (!mExpanded) {
      animateOpen();
    } else {
      animateClose();
    }
  }

  public void open() {
    openDrawer();
    invalidate();
    requestLayout();
    sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
  }

  public void close() {
    closeDrawer();
    invalidate();
    requestLayout();
  }

  public void animateClose() {
    prepareContent();
    if (mOnDrawerScrollListener != null) {
      mOnDrawerScrollListener.onScrollStarted();
    }
    animateClose(mVertical ? mHandle.getTop() : mHandle.getLeft(), false);
    if (mOnDrawerScrollListener != null) {
      mOnDrawerScrollListener.onScrollEnded();
    }
  }

  public void animateOpen() {
    prepareContent();
    if (mOnDrawerScrollListener != null) {
      mOnDrawerScrollListener.onScrollStarted();
    }
    animateOpen(mVertical ? mHandle.getTop() : mHandle.getLeft(), false);
    sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
    if (mOnDrawerScrollListener != null) {
      mOnDrawerScrollListener.onScrollEnded();
    }
  }

  @Override
  public CharSequence getAccessibilityClassName() {
    return SlidingDrawer.class.getName();
  }

  private void closeDrawer() {
    moveHandle(COLLAPSED_FULL_CLOSED);
    mContent.setVisibility(View.GONE);
    mContent.destroyDrawingCache();
    if (!mExpanded) {
      return;
    }
    mExpanded = false;
    if (mOnDrawerCloseListener != null) {
      mOnDrawerCloseListener.onDrawerClosed();
    }
  }

  private void openDrawer() {
    moveHandle(EXPANDED_FULL_OPEN);
    mContent.setVisibility(View.VISIBLE);
    if (mExpanded) {
      return;
    }
    mExpanded = true;
    if (mOnDrawerOpenListener != null) {
      mOnDrawerOpenListener.onDrawerOpened();
    }
  }

  public void setOnDrawerOpenListener(@Nullable OnDrawerOpenListener listener) {
    mOnDrawerOpenListener = listener;
  }

  public void setOnDrawerCloseListener(@Nullable OnDrawerCloseListener listener) {
    mOnDrawerCloseListener = listener;
  }

  public void setOnDrawerScrollListener(@Nullable OnDrawerScrollListener listener) {
    mOnDrawerScrollListener = listener;
  }

  @NonNull
  public View getHandle() {
    return mHandle;
  }

  @NonNull
  public View getContent() {
    return mContent;
  }

  public void unlock() {
    mLocked = false;
  }

  public void lock() {
    mLocked = true;
  }

  public boolean isOpened() {
    return mExpanded;
  }

  public boolean isMoving() {
    return mTracking || mAnimating;
  }

  public interface OnDrawerOpenListener {
    void onDrawerOpened();
  }

  public interface OnDrawerCloseListener {
    void onDrawerClosed();
  }

  public interface OnDrawerScrollListener {
    void onScrollStarted();

    void onScrollEnded();
  }

  private class DrawerToggler implements OnClickListener {
    public void onClick(View v) {
      if (mLocked) {
        return;
      }
      if (mAnimateOnClick) {
        animateToggle();
      } else {
        toggle();
      }
    }
  }

  private class ContentViewBitmapCache implements ViewTreeObserver.OnGlobalLayoutListener {

    @Override
    public void onGlobalLayout() {
      final int contentWidth = mContent.getWidth();
      final int contentHeight = mContent.getHeight();
      if (contentWidth > 0 && contentHeight > 0) {
        mBitmap = Bitmap.createBitmap(contentWidth, contentHeight, Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas(mBitmap);
      }
    }
  }
}
